#pragma once

#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "comm.hpp"
#include "InetAddr.hpp"


// 封装套接字
// 采用模板方法策略模式

namespace SocketModule
{
    static const int defaultbacklog = 15;
    class Socket
    {
    public:
        virtual ~Socket(){}
        virtual void SocketOrDie() = 0;
        virtual void BindOrDie(uint16_t port) = 0;
        virtual void ListenOrDie(int backlog) = 0;
        virtual std::shared_ptr<Socket> Accept(InetAddr * addr) = 0;
        virtual int Connect(InetAddr &addr) = 0;
        virtual void Close() = 0;
        virtual int Recv(std::string *out) = 0;
        virtual int Send(const std::string &out) = 0;
    public:
        void BuildTcpSocket(uint16_t port, int backlog = defaultbacklog)
        {
            SocketOrDie();
            BindOrDie(port);
            ListenOrDie(backlog);
        }

        void BuildTcpClientSocket()
        {
            SocketOrDie();
        }
    };

    class TcpSocket : public Socket
    {
    public:
        TcpSocket() :_sockfd(-1) {}
        TcpSocket(int sockfd) : _sockfd(sockfd) {}
        ~TcpSocket(){}

        void SocketOrDie() override
        {
            _sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
            if(_sockfd < 0){
                LOG(loglevel::FATAL) << "create socket error";
                exit(SOCKET_ERR);
            }
            LOG(loglevel::INFO) << "create socket success: " << _sockfd;
        }

        void BindOrDie(uint16_t port) override
        {
            InetAddr local_addr(port);
            int n = ::bind(_sockfd, local_addr.NetAddr(), local_addr.Netaddrlen());
            if(n < 0){
                LOG(loglevel::FATAL) << "bind socket error";
                exit(BIND_ERR);
            }
            LOG(loglevel::INFO) << "bind socket success: " << _sockfd;
        }

        void ListenOrDie(int backlog) override
        {
            int n = ::listen(_sockfd, backlog);
            if(n < 0){
                LOG(loglevel::FATAL) << "listen socket error";
                exit(LISTEN_ERR);
            }
            LOG(loglevel::INFO) << "listen socket success: " << _sockfd;
        }

        std::shared_ptr<Socket> Accept(InetAddr * addr) override
        {
            // accept需要返回客户端的网络信息
            struct sockaddr_in client;
            socklen_t len = sizeof(client); 
            int sockfd = ::accept(_sockfd, CONVERT(client), &len);
            if(sockfd < 0){
                LOG(loglevel::WARINING) << "accpet warining";
            }
            LOG(loglevel::INFO) << "accept success ->" << sockfd;
            
            addr->SetNetaddr(client); // 将客户端信息带出去 
            return std::make_shared<TcpSocket>(sockfd); // accept返回的文件描述符是服务端用来于客户端IO的窗口
                                                  // 如果直接返回一个文件描述符的话，就会导致listensockfd 与 sockfd接口调用不一致
        }

        int Connect(InetAddr &addr) { return ::connect(_sockfd, addr.NetAddr(), addr.Netaddrlen()); }

        void Close() override { if(_sockfd >= 0) ::close(_sockfd); }

        int Recv(std::string *out) override
        {
            char buffer[4096 * 2];
            ssize_t n = ::recv(_sockfd, buffer, sizeof(buffer) - 1, 0);
            if(n > 0){
                buffer[n] = 0;
                *out += buffer;
            }
            return n;
        }

        int Send(const std::string &out) override { return ::send(_sockfd, out.c_str(), out.size(), 0); }

    private:
        int _sockfd;
    };
}